//
//  CalculatorEngine.m
//  iCalculator
//
//  Created by Stephen Houser on 9/7/07.
//  Copyright 2007 __MyCompanyName__. All rights reserved.
//

#import "CalculatorEngine.h"

@implementation CalculatorEngine

/* 
 * Utility method to dump diagnostic information to "console"
 */
- (void) dumpAll: (NSString *)message {
/*
	NSLog(message);
	NSLog(@"\tdisplay          : %@", display);
	NSLog(@"\tdisplayValue     : %g", displayValue);
	NSLog(@"\tmemory           : %@", memory);
	NSLog(@"\tmemoryValue      : %g", memoryValue);
	NSLog(@"\taccumulator      : %@", accumulator);
	NSLog(@"\taccumulatorValue : %g", accumulatorValue);
	NSLog(@"\toperation        : %@", operation);
*/
	NSLog(@"%@: d([%@],%g), a([%@],%g), m([%@],%g), o(%@,%@), d(%@, %@)", 
		message,
		display, displayValue,
		accumulator, accumulatorValue,
		memory, memoryValue,
		currentOperation, lastOperation,
		(resetDisplay ? @"T" : @"F"),
		(isResult ? @"T" : @"F")
		);
}

/* 
 * Initialize the engine, clear any pertinent "registers" and get
 * ready for calculations.
 */
-(id)init {
	[self clearAll];
	[self memoryClear];
	return self;
}

/*
 * Return the String value of the current display register.
 * NOTE: the display value should never return Nil to outsiders.
 */
- (NSString *) getDisplay {
	if (display == Nil) {
		[self setDisplay: Nil];
	}
	
	return display;
}

/*
 * Set the string value of the display register.
 */
- (void) setDisplay: (NSString *)newDisplay {
	//NSLog(@"CalculatorEngine setDisplay: [%@]", newDisplay);	
	if (display != Nil) {
		[display release];
	}

	if (newDisplay == Nil) {
		newDisplay = @"";
	}

	display = [[NSMutableString alloc] initWithString: newDisplay];
	displayValue = 0.0f; // bogus value
}

/*
 * Set the float value of the display register.
 * This routine needs to convert the passed value to the proper
 * string version -- as the definitive source is the string.
 */
- (void) setDisplayValue: (float)newDisplayValue {
	//NSLog(@"CalculatorEngine setDisplay: [%@]", newDisplayValue);	
	if (display != Nil) {
		[display release];
	}
	
	display = [[NSMutableString alloc] initWithFormat: @"%g", newDisplayValue];	
	displayValue = newDisplayValue;
}

/*
 * Get the current accumulator.
 */
- (NSString *) getAccumulator {
	return accumulator;
}

/*
 * Set the current accumulator.
 */
- (void) setAccumulator: (NSString *)newAccumulator {
	//NSLog(@"CalculatorEngine setAccumulator: [%@]", newAccumulator);
	if (accumulator != Nil) {
		[accumulator release];
	}
	
	accumulator = Nil;
	if (newAccumulator != Nil) {
		accumulator = [[NSString alloc] initWithString: newAccumulator];
	}
	
	accumulatorValue = 0.0f;  // bogus value
}

/* 
 * Set the accumulator from a float value. As with the display
 * register, this needs to set the difinitive string
 * version of the accumulator.
 */
- (void) setAccumulatorValue: (float)newAccumulatorValue {
	//NSLog(@"CalculatorEngine setAccumulatorValue: [%@]", newAccumulatorValue);	
	if (accumulator != Nil) {
		[accumulator release];
	}
	
	accumulator = [[NSString alloc] initWithFormat: @"%g", newAccumulatorValue];	
	accumulatorValue = newAccumulatorValue;  // bogus value
}

/*
 * Get the current "memory".
 */
- (NSString *) getMemory {
	return memory;
}

/* 
 * Set the current "memory".
 */
- (void) setMemory: (NSString *)newMemory {
	//NSLog(@"CalculatorEngine setMemory: [%@]", newMemory);
	if (memory != Nil) {
		[memory release];
	}
	
	memory = Nil;
	if (newMemory != Nil) {
		memory = [[NSString alloc] initWithString: newMemory];
	}
	
	memoryValue = 0.0f; // bogus value
}

/* 
 * Set the current "memory" from a float value.
 * As others, must set the definitive string version.
 */
- (void) setMemoryValue: (float)newMemoryValue {
	//NSLog(@"CalculatorEngine setMemoryValue: [%@]", newMemoryValue);	
	if (memory != Nil) {
		[memory release];
	}
	
	memory = [[NSString alloc] initWithFormat: @"%g", newMemoryValue];	
	memoryValue = newMemoryValue;  // bogus value
}

/*
 * Utility routine to materialize the string (definitive) values
 * into floating point values so that we can use them in
 * actual mathematical calculations. Herein each NSString value
 * is parsed to a floating point version of itself. Not much error
 * checking on what gets set, that's left to atof(), which, of course
 * is deprecated by Apple.
 *
 * FLOAT_HACK
 * To some degree this routine exists because of some
 * problems compilng for the iPhone. I cannot seem to return
 * float values in methods without suffering _objc_msgSent_fpret
 * unresolved errors in the linker.
 */
- (void) materializeValues {
	// materialize the display/working number
	if (display != Nil) {
		displayValue = atof([display UTF8String]);
	} else {
		displayValue = 0.0f;
	}

	// materialize the current accumulator value
	if (accumulator != Nil) {
		accumulatorValue = atof([accumulator UTF8String]);
	} else {
		accumulatorValue = 0.0f;
	}

	// materialize the memory value
	if (memory != Nil) {
		memoryValue = atof([memory UTF8String]);
	} else {
		memoryValue = 0.0f;
	}
}

/*
 * Interface to the keypad and "controllers." This routine accepts "commands"
 * (loosely -- key press events) from the controller of the engine. This is
 * where keys get sent in to be processed. Some keys produce changes in the
 * display, accumulator, memory, etc. So somebody calling this should really think
 * about calling "getDisplay" sometime afterwards to see what things look like.
 * Though if you were a program, you might not want all those intermediate results.
 */
- (void)sendCommand: (NSString *)command {
	//NSLog(@"CalculatorEngine sendCommand: [%@]", command);
	
	// I really hate how this is done here.
	// I forsee a table lookup in your future...
	if ([command isEqualToString: @"c"]) {
		[self clearAll];
	} else if ([command isEqualToString: @"+"]) {
		[self binaryOperator: command];
	} else if ([command isEqualToString: @"-"]) {
		[self binaryOperator: command];
	} else if ([command isEqualToString: @"*"]) {
		[self binaryOperator: command];
	} else if ([command isEqualToString: @"/"]) {
		[self binaryOperator: command];
	} else if ([command isEqualToString: @"="]) {
		[self equals];
	} else if ([command isEqualToString: @"mr"]) {
		[self memoryRecall];
	} else if ([command isEqualToString: @"m+"]) {
		[self memoryAdd];
	} else if ([command isEqualToString: @"m-"]) {
		[self memorySubtract];
	} else {
		// catches everything else -- even things it should not
		// the decimal point is good to collect, but... not other
		// things.
		[self digit: [command characterAtIndex: 0]];
	}
}

/*
 * Digit collection routine. This routine collects keypressed
 * that we hope are digits and perhaps a decimal point. It appends
 * them to the display register for later use.
 */
- (void) digit: (char)digit {
	[self dumpAll: @">digit"];
	
	// This gets set right after an operation.
	// It is so users "see" the result of their previous operation
	// and when they hit another digit is clears it away.
	if (isResult == YES) {
		[self clearEntry];
	} else if (resetDisplay == YES) {
		// push the result into the accumulator and start taking a new number
		[self setAccumulator: [self getDisplay]];
		[self clearEntry];
	}
	
	[display appendFormat: @"%c", digit];

	[self dumpAll: @"<digit"];
}

/*
 * System Operation to clear the display register.
 * Resets to most recent digit accumulation state.
 */
- (void)clearEntry {
	[self dumpAll: @">clearEntry"];
	
	[self setDisplay: Nil];
	resetDisplay = NO;

	[self dumpAll: @"<clearEntry"];
}

/*
 * System operation to clear entry and accumulator and reset
 * to initial digit collection state.
 */
- (void)clearAll {
	[self dumpAll: @">clear"];

	[self setDisplay: Nil];
	[self setAccumulator: Nil];
	lastOperation = Nil;
	currentOperation = Nil;
	resetDisplay = NO;
	isResult = NO;
		
	[self dumpAll: @"<clear"];
}

- (void) innerEvaluateOperation: (NSString *)operation {
	[self dumpAll: @">innerEvaluateOperaton"];

	[self materializeValues];		

	if ([operation isEqualToString: @"+"]) {
		[self add];
	} else if ([operation isEqualToString: @"-"]) {
		[self subtract];
	} else if ([operation isEqualToString: @"*"]) {
		[self multiply];
	} else if ([operation isEqualToString: @"/"]) {
		[self divide];
	}
	

	[self dumpAll: @"<innerEvaluateOperaton"];
}

- (void) evaluateOperation {
	[self dumpAll: @">evaluateOperaton"];

	if ((currentOperation != Nil) || (lastOperation != Nil)) {
		if (currentOperation == Nil) {
			[self setAccumulator: [self getDisplay]];
			[self innerEvaluateOperation: lastOperation];
		} else {
			[self innerEvaluateOperation: currentOperation];
			lastOperation = currentOperation;
		}
				
		//[self setDisplay: [self getAccumulator]];
	}

	[self dumpAll: @"<evaluateOperaton"];
}

/*
 * Utility routne called when a binary operator is wanted.
 * This routine sets up the "pending operaton" as the operator
 * that was given and moves the display to the accumulator and
 * moves to collecting a second number.
 */
- (void) binaryOperator: (NSString *)operator {
	[self dumpAll: @">binaryOperator"];

	// if we hit an operater with something already in
	// the accumulator, we act a little like an equals
	// operator and operate & display.
	if ((accumulator != Nil) && (isResult == NO) && (resetDisplay == NO)) {
		[self evaluateOperation];
	} else {
		[self setAccumulator: [self getDisplay]];	
	}
	
	currentOperation = operator;
	resetDisplay = YES;
	
	[self dumpAll: @"<binaryOperator"];
}

/*
 * Perform the equals operation -- carry out any pending operations
 * from the binaryOperator and set the display and accumulators appropriately.
 */
- (void)equals {
	[self dumpAll: @">equals"];
	
	[self evaluateOperation];
	isResult = YES;
	currentOperation = Nil;
	
	[self dumpAll: @"<equals"];
}

/*
 * Perform an add operation on the accumulator and display registers.
 */
- (void)add {
	[self dumpAll: @">add"];
	
	[self setDisplayValue: (accumulatorValue + displayValue)];

	[self dumpAll: @"<add"];
}

/*
 * Perform a subtraction operation on the accumulator and display registers.
 */
- (void)subtract {
	[self dumpAll: @">subtract"];

	[self setDisplayValue: (accumulatorValue - displayValue)];

	[self dumpAll: @"<subtract"];
}

/*
 * Perform a multiplicaton operation on the accumulator and display registers.
 */
- (void)multiply {
	[self dumpAll: @">multiply"];

	[self setDisplayValue: (accumulatorValue * displayValue)];

	[self dumpAll: @"<multiply"];
}

/*
 * Perform a division operation on the accumulator and display registers.
 */
- (void)divide {
	[self dumpAll: @">divide"];

	[self setDisplayValue: (accumulatorValue / displayValue)];

	[self dumpAll: @"<divide"];
}

/*
 * Clear the contents of the memory register.
 */
- (void) memoryClear {
	[self dumpAll: @">memoryClear"];

	if (memory != Nil) {
		[memory release];
	}

	memory = Nil;
	memoryValue = 0.0f;

	[self dumpAll: @">memoryClear"];
}

/*
 * Add the display register to the memory register.
 * The result goes to memory not the display.
 */
- (void)memoryAdd {
	[self dumpAll: @">memoryAdd"];

	[self materializeValues];
	[self setMemoryValue: (memoryValue + displayValue)];
	resetDisplay = YES;

	[self dumpAll: @"<memoryAdd"];
}

/*
 * Subtract the display register to the memory register.
 * The result goes to memory not the display.
 */
- (void)memorySubtract {
	[self dumpAll: @">memorySubtract"];

	[self materializeValues];
	[self setMemoryValue: (memoryValue - displayValue)];
	resetDisplay = YES;

	[self dumpAll: @"<memorySubtract"];
}

/*
 * Recall the memory register to the display register.
 */
- (void)memoryRecall {
	[self dumpAll: @">memoryRecall"];

	[self materializeValues];
	if (memory != Nil) {
		[self setDisplay: [self getMemory]];
	} else {
		[self setDisplay: @""];
	}
	
	resetDisplay = YES;

	[self dumpAll: @"<memoryRecall"];
}

@end
